/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.cf.direct;

import com.android.dx.cf.iface.Attribute;
import com.android.dx.cf.iface.ParseException;
import com.android.dx.cf.iface.ParseObserver;
import com.android.dx.cf.iface.StdAttributeList;
import com.android.dx.util.ByteArray;
import com.android.dx.util.Hex;

/**
 * Parser for lists of attributes.
 */
final/* package */class AttributeListParser {

	/** {@code non-null;} the class file to parse from */
	private final DirectClassFile cf;

	/** attribute parsing context */
	private final int context;

	/** offset in the byte array of the classfile to the start of the list */
	private final int offset;

	/** {@code non-null;} attribute factory to use */
	private final AttributeFactory attributeFactory;

	/** {@code non-null;} list of parsed attributes */
	private final StdAttributeList list;

	/**
	 * {@code >= -1;} the end offset of this list in the byte array of the
	 * classfile, or {@code -1} if not yet parsed
	 */
	private int endOffset;

	/** {@code null-ok;} parse observer, if any */
	private ParseObserver observer;

	/**
	 * Constructs an instance.
	 * 
	 * @param cf
	 *            {@code non-null;} class file to parse from
	 * @param context
	 *            attribute parsing context (see {@link AttributeFactory})
	 * @param offset
	 *            offset in {@code bytes} to the start of the list
	 * @param attributeFactory
	 *            {@code non-null;} attribute factory to use
	 */
	public AttributeListParser(DirectClassFile cf, int context, int offset,
			AttributeFactory attributeFactory) {
		if (cf == null) {
			throw new NullPointerException("cf == null");
		}

		if (attributeFactory == null) {
			throw new NullPointerException("attributeFactory == null");
		}

		int size = cf.getBytes().getUnsignedShort(offset);

		this.cf = cf;
		this.context = context;
		this.offset = offset;
		this.attributeFactory = attributeFactory;
		this.list = new StdAttributeList(size);
		this.endOffset = -1;
	}

	/**
	 * Sets the parse observer for this instance.
	 * 
	 * @param observer
	 *            {@code null-ok;} the observer
	 */
	public void setObserver(ParseObserver observer) {
		this.observer = observer;
	}

	/**
	 * Gets the end offset of this constant pool in the {@code byte[]} which it
	 * came from.
	 * 
	 * @return {@code >= 0;} the end offset
	 */
	public int getEndOffset() {
		parseIfNecessary();
		return endOffset;
	}

	/**
	 * Gets the parsed list.
	 * 
	 * @return {@code non-null;} the list
	 */
	public StdAttributeList getList() {
		parseIfNecessary();
		return list;
	}

	/**
	 * Runs {@link #parse} if it has not yet been run successfully.
	 */
	private void parseIfNecessary() {
		if (endOffset < 0) {
			parse();
		}
	}

	/**
	 * Does the actual parsing.
	 */
	private void parse() {
		int sz = list.size();
		int at = offset + 2; // Skip the count.

		ByteArray bytes = cf.getBytes();

		if (observer != null) {
			observer.parsed(bytes, offset, 2, "attributes_count: " + Hex.u2(sz));
		}

		for (int i = 0; i < sz; i++) {
			try {
				if (observer != null) {
					observer.parsed(bytes, at, 0, "\nattributes[" + i + "]:\n");
					observer.changeIndent(1);
				}

				Attribute attrib = attributeFactory.parse(cf, context, at,
						observer);

				at += attrib.byteLength();
				list.set(i, attrib);

				if (observer != null) {
					observer.changeIndent(-1);
					observer.parsed(bytes, at, 0, "end attributes[" + i + "]\n");
				}
			} catch (ParseException ex) {
				ex.addContext("...while parsing attributes[" + i + "]");
				throw ex;
			} catch (RuntimeException ex) {
				ParseException pe = new ParseException(ex);
				pe.addContext("...while parsing attributes[" + i + "]");
				throw pe;
			}
		}

		endOffset = at;
	}
}
